(ns group-travel.core
  (:use [clojure-csv.core]
         [clojure.math.numeric-tower]
         [clojure.pprint])
	(:require [clojure.string :as s] ))

(def flight-data (parse-csv (slurp "schedule.txt")))

(def people {"Seymour" "BOS", "Franny" "DAL", "Zooey" "CAK", "Walt" "MIA", "Buddy" "ORD", "Les" "OMA"})

(def destination "LGA")

(defn sort-flights
  [data] 
  (reduce (fn [result [origin dest depart arrive price]] 
              (if (result [origin dest])
                (assoc result [origin dest] (conj (result [origin dest]) [depart arrive price])) 
                (assoc result [origin dest] [[depart arrive price]]))) 
          {} data))
			
(def sorted-flights (sort-flights flight-data))

(defn minutes [str]
	(let [[h m] (s/split str #":")]
   (+ (* 60 (Integer/parseInt h))
      (Integer/parseInt m))))   

(def test-schedule [[1 4] [3 2] [7 3] [6 3] [2 4] [5 3]])

(defn print-schedule 
  [people flights sch dest]
	(pprint (map (fn [[o r] [name origin]]
                [name origin ((flights [origin dest])  o)
                 ((flights [dest origin]) r)])
              sch people)))

(defn schedule-cost 
  [sch people flights dest]
  (let [ar-dep-times (into {} (map (fn [[o r] [name origin]]
                                     {(minutes (((flights [origin dest]) o) 1))
                                      (minutes (((flights [dest origin]) r) 0))})
                                   sch people))
        latest-arrival (apply max (keys ar-dep-times))
        earliest-dep (apply min (vals ar-dep-times))
        car-rental-extra (if (< latest-arrival earliest-dep) 50 0)]
    (+ (apply + (map (fn [[o r] [name origin]]
                       (let [out-flight ((flights [origin dest]) o)
                             return-flight ((flights [dest origin]) r)]
                         (+ (Integer/parseInt (out-flight 2)) 
                            (Integer/parseInt (return-flight 2))
                            (- latest-arrival (minutes (out-flight 1)))
                            (- (minutes (return-flight 0)) earliest-dep))))
                        sch people )) 
          car-rental-extra)))

(def domain [[0 8 0 8] [0 8 0 8] [0 8 0 8] [0 8 0 8] [0 8 0 8] [0 8 0 8]])

(defn random-solution
  [domain]
  (reduce (fn [result [b c d e]]
            (conj result [(+ (rand-int (- (inc c) b)) b)
                          (+ (rand-int (- (inc e) d)) d)])) [] domain)) 

(defn random-optimize
  [domain costf people sorted-flights destination]
    (loop [[best-cost best-result] [999999999 nil] 
           n 100000]
      (if (< n 1)
          [best-cost best-result]
        (let [random-solution (random-solution domain)
              cost (costf random-solution people sorted-flights destination)]
          (recur (if (< cost best-cost)
                   [cost random-solution]
                   [best-cost best-result]) (dec n))))))

(defn neighbors
  [domain solution]
  (map (fn [[a b c d] [o r] i]
         (reduce (fn [result j] (conj result 
                                      (if (< ((solution i) j) b) (update-in solution [i j] inc))
                                      (if (> ((solution i) j) a) (update-in solution [i j] dec)))) [] [0 1]))
       domain solution (range (count domain))))

(defn hill-climb
  [domain costf people flights destination]
    (loop [result (random-solution domain)]
      (let [neighbors (remove nil? (reduce (fn [a b] (apply merge a b)) (neighbors domain result)))
           costs (reduce (fn [r s] (assoc r (costf s people flights destination) s)) {} neighbors)
           curr-cost (apply min (keys costs))
           res-cost (costf result people flights destination)]
           (if (< res-cost curr-cost)
               [res-cost result]
             (recur (costs curr-cost))))))

(defn mutated-value
  [val domain i step]
  (let [value (+ (rand-nth [(- step) step]) val)
        min ((domain i) 0)
        max ((domain i) 1)] 
    (if (< value min) min
      (if (> value max) max value))))

(defn mutate
  [solution step domain]
  (let [i (rand-int (count domain))]
    (update-in solution [i (rand-int 2)] mutated-value domain i step)))


(defn annealing-optimize
  [domain costf people flights dest]
      (loop [result (random-solution domain)
             temp 100000.0]
      (if (< temp 0.1)
          [(costf result people flights dest) result]
        (let [solution (mutate result 1 domain)
              cost (costf solution people flights dest)
              best-cost (costf result people flights dest)
              p (expt Math/E (/ (- (- cost best-cost)) temp))]
          (recur (if (or (< cost best-cost) (< (rand) p)) solution result) 
                 (* temp 0.95))))))

(defn cross-over
  [sol1 sol2]
  (let [i (rand-int (count sol1))]
    (apply conj (subvec sol1 0 i) (subvec sol2 i))))

(defn my-repeatedly
  ([f arg] (lazy-seq (cons (f arg) (repeatedly f arg))))
  ([n f arg] (take n (repeatedly f arg))))

(defn populate
  [size domain list]
  (loop [n size
         result []]
    (if (< n 1)
      (apply conj list result)
      (let [value (if (< (rand) 0.8)
                    (mutate (rand-nth list) 1 domain)
                    (cross-over (rand-nth list) (rand-nth list)))]
        (recur (dec n) (conj result value))))))

(defn genetic-optimize
  [domain costf pop-size people flights dest]
  (let [elite (int (* 0.2 pop-size))
        pop (my-repeatedly pop-size random-solution domain)]
    (loop [n 100
           result nil
           population pop]
      (if (< n 1)
        [(costf result people flights dest) result]
        (let [costs (reduce (fn [x y] (assoc x (costf y people flights dest)
                                            y)) {} population)
              ranked  (take elite (vals (sort costs)))
              popul   (populate (- pop-size elite) domain ranked)]
          (recur (dec n) (first ranked) popul))))))


